package com.server.zk;

import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryOneTime;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.Stat;

import java.net.InetSocketAddress;

/**
 * @author Zixiang Hu
 * @description
 * @create 2021-03-04-16:57
 */
@Slf4j
public class ZkService {
    private CuratorFramework client;

    public ZkService(String connectString) {
        client = CuratorFrameworkFactory.builder().connectString(connectString)
                // 会话超时时间
                .sessionTimeoutMs(5000)
                // 超时重连策略
                .retryPolicy(new RetryOneTime(1000))
                // 配置默认的工作空间
                .namespace("myrpc")
                .build();
        client.start();
        log.info("连接Zookeeper服务器成功，服务器地址为：{}", connectString);
    }

    /**
     * 注册一个服务
     * 如果该服务已经存在，则添加一个提供该服务的服务器的地址；如果不存在，则将该服务注册到zk中并添加提供该服务的服务器地址
     *
     * @param serviceName   提供的服务名称
     * @param serverAddress 提供该服务的服务器地址
     */
    public void registerService(String serviceName, InetSocketAddress serverAddress) {
        try {
            Stat stat = client.checkExists().forPath("/" + serviceName);
            // 说明该服务之前不存在，则这是一个新提供的服务，因此创建一个服务节点
            if (stat == null) {
                client.create()
                        .creatingParentsIfNeeded()
                        // 创建的服务节点设置为持久节点，不会随着zk的关闭消失
                        .withMode(CreateMode.PERSISTENT)
                        // 权限设置 world:anyone:cwrda，可以根据实际情况来设置，这里为了方便直接赋予所有权限
                        .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
                        .forPath("/" + serviceName);
            }
            // 继续在该服务下创建一个表示提供该服务的服务器地址的节点
            // 例如UserService服务可能有多个不同的服务器提供，这里暂不实现负载均衡
            client.create()
                    .creatingParentsIfNeeded()
                    // 这里创建的是临时节点，只要服务器一关闭，则该节点就会消失
                    .withMode(CreateMode.EPHEMERAL)
                    // InetSocketAddress实例的toString方法直接就是 /x.x.x.x:port形式
                    .forPath("/" + serviceName + serverAddress.toString());

        } catch (Exception e) {
            client.close();
            log.error("ZkService的registerService发生异常，客户端被迫关闭，异常信息是：{}", e.getMessage());
        }
    }
}
